#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import BaseHTTPServer
import SocketServer
import base64
import cgi
import datetime
import os
import signal
import string
import sys
import threading
import time
import subprocess
import pam


HK_PEROID = 1                   # Housekeeping peroid
SESS_EXPIRE = 5*60              # How long(in seconds) untouched a session can be before being removed.

HTTP_HD_SESS_KEY='hssid'      # The sessionID key used to set session cookie
SESS_LAST_UDATE='lud'
SESS_POPEN='popen'
COOKIE_MATCH=r'^.*\b%s=(.+);.*$' % HTTP_HD_SESS_KEY



class MyHTTPServer(SocketServer.ThreadingMixIn,BaseHTTPServer.HTTPServer):
    pass #TODO

class MyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    protocol_version="HTTP/1.1"
    server_version="httpshell/1.0"

    def do_GET(self): # Only for request for homepage
        self.cookie=self.get_cookie()
        sessid=self.cookie.get(HTTP_HD_SESS_KEY)
        if sessid:
            self.session=sess_engine.storage.get(sessid)
            if self.session:
                self.session=sess_engine.touch(sessid)
                self.sessid=sessid
            else:
                self.sessid,self.session=sess_engine.init_session()
                sessid=None
        else:
            self.sessid,self.session=sess_engine.init_session()
        if self.path == '/':
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            if not sessid:
                self.send_header('Set-Cookie', '%s=%s'%(HTTP_HD_SESS_KEY,self.sessid))
            self.end_headers()
            
            page_data={} #TODO populate it for rendering homepage
            self.wfile.write(templates['home.html'].safe_substitute(page_data))
        else:
            self.reject(501) # Not Implemented
            
    def do_POST(self):
        self.cookie=self.get_cookie()
        sessid=self.cookie.get(HTTP_HD_SESS_KEY)
        if sessid:
            self.session=sess_engine.storage.get(sessid)
            if self.session:
                self.session=sess_engine.touch(sessid)
                self.sessid=sessid
            else:
                return self.redirect() # No session found, direct to homepage(i.e., login)
        else:
            return self.reject(401) # Unauthorized
        if self.path=='/login':
            return self.login()
        elif self.path=='/logout':
            return self.logout()
        elif self.path=='/cmd':
            return self.run_cmd()
        else:
            return self.reject(501) # Not Implemented
        
    def reject(self,code=501):
        self.send_response(code) 
        self.send_header('Content-length', '0')
        self.end_headers()
    def redirect(self,url='/'): #TODO should I convert the url to an absolute url ?
        self.send_response(302) # redirect 
        self.send_header('Location', url)
        self.end_headers()

    def get_cookie(self):
        cookies=[item.partition('=') for item in self.headers['Cookie'].split(';')]
        return dict([(k,v) for k,_,v in cookies])
        
    def login(self):
        form=self.parse_form()
        uname=form['uname']
        pwd=form['pwd']
        self.send_response(200)
        self.send_header('Content-type', 'text/json')
        self.end_headers()
        if pam.authenticate(uname, pwd):
            self.session[SESS_POPEN]=None
            self.wfile.write('1')
        else:
            self.wfile.write('0')
    
    def logout(self):
        pass
    
    def run_cmd(self):
#        self.send_header('Content-type', 'text/event-stream')
        pass
    
    def start_shell(self):
        pass #TODO
    
    def parse_form(self):
        form = cgi.FieldStorage(
                fp=self.rfile, 
                headers=self.headers,
                environ={'REQUEST_METHOD':'POST',
                         'CONTENT_TYPE':self.headers['Content-Type'],
                         })
        return form

class SessionEngine(object):
    def __init__(self):
        self.storage={}

    def clear_storage(self,sessid=None):
        
        def remove_sess(sid):
            sess=self.storage.get(sid)
            if not sess:return 0
            proc=self.storage[sid].get(SESS_POPEN)
            if proc:
                os.kill(proc.pid, signal.SIGKILL)
            del self.storage[sid]
            return 1
        
        if sessid:
            return remove_sess(sessid)
        
        ntime=datetime.datetime.now()
        def _lifetime(sess):
            td=ntime-sess[SESS_LAST_UDATE]
            return td.seconds+td.days*24*3600
        lifetimes=[(sessid,_lifetime(sess)) for sessid, sess in self.storage]
        return sum([remove_sess(sid) for sid,t in lifetimes if t > SESS_EXPIRE])
    
    def init_session(self):
        sessid=base64.b64encode(os.urandom(32))
        return sessid,self.touch(sessid)
        
    def touch(self,sessid):
        session=self.storage.get(sessid)
        if session:
            session[SESS_LAST_UDATE]=datetime.datetime.now()
        else:
            self.storage[sessid]={SESS_LAST_UDATE:datetime.datetime.now()}
        return session

def load_templates():
    fs=[(f, open(f)) for f in os.listdir('./webpage')]
    tmpl_dict=dict([(fname,string.Template(f.read())) for fname,f in fs])
    [f.close() for _,f in fs]
    return tmpl_dict

def housekeeping():
    while 1:
        sess_engine.clear_storage()
        time.sleep(HK_PEROID)

def start_housekeeping():
    housekeeper=threading.Thread(target=housekeeping,name='hs-housekeeping')
    housekeeper.daemon=True
    housekeeper.start()

def start_httpd():
    if sys.argv[1:]:
        port = int(sys.argv[1])
    else:
        port = 8765

    httpd = MyHTTPServer(('',port), MyRequestHandler)
    sa = httpd.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    httpd.serve_forever()
    
sess_engine=SessionEngine()
templates=load_templates()

if __name__ == "__main__":
    start_housekeeping()
    start_httpd()

